home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / news / inn1.000 / inn1.4sec-linux-src.tar / inn / nnrpd / nnrpd.c < prev    next >
C/C++ Source or Header  |  1993-03-18  |  17KB  |  676 lines

  1. /*  $Revision: 1.18 $
  2. **
  3. **  NNTP server for readers (NNRP) for InterNetNews.
  4. **  This server doesn't do any real load-limiting, except for what has
  5. **  proven empirically necesary (i.e., look at GRPscandir).
  6. */
  7. #define MAINLINE
  8. #include <signal.h>
  9. #include "nnrpd.h"
  10. #include <sys/time.h>
  11. #include <netdb.h>
  12. #if    defined(HPUX)
  13. #include <sys/pstat.h>
  14. #endif    /* defined(HPUX) */
  15.  
  16.  
  17. #define CMDany        -1
  18.  
  19.  
  20. typedef struct _CMDENT {
  21.     STRING    Name;
  22.     FUNCPTR    Function;
  23.     BOOL    Needauth;
  24.     int        Minac;
  25.     int        Maxac;
  26.     STRING    Help;
  27. } CMDENT;
  28.  
  29.  
  30. char    NOACCESS[] = NNTP_ACCESS;
  31. char    ACTIVE[] = _PATH_ACTIVE;
  32. char    ACTIVETIMES[] = _PATH_ACTIVETIMES;
  33. char    HISTORY[] = _PATH_HISTORY;
  34. char    NEWSGROUPS[] = _PATH_NEWSGROUPS;
  35.     /* Default permission -- change with adb. */
  36. BOOL    PERMdefault = FALSE;
  37.  
  38. STATIC double    STATstart;
  39. STATIC double    STATfinish;
  40. #if    !defined(HPUX)
  41. STATIC char    *TITLEstart;
  42. STATIC char    *TITLEend;
  43. #endif    /* !defined(HPUX) */
  44. STATIC SIGVAR    ChangeTrace;
  45.  
  46. extern FUNCTYPE    CMDauthinfo();
  47. extern FUNCTYPE    CMDdate();
  48. extern FUNCTYPE    CMDfetch();
  49. extern FUNCTYPE    CMDgroup();
  50. STATIC FUNCTYPE    CMDhelp();
  51. extern FUNCTYPE    CMDlist();
  52. extern FUNCTYPE    CMDmode();
  53. extern FUNCTYPE    CMDnewgroups();
  54. extern FUNCTYPE    CMDnewnews();
  55. extern FUNCTYPE    CMDnextlast();
  56. extern FUNCTYPE    CMDpost();
  57. extern FUNCTYPE    CMDxgtitle();
  58. extern FUNCTYPE    CMDxhdr();
  59. extern FUNCTYPE    CMDxover();
  60. extern FUNCTYPE    CMDxpat();
  61. extern FUNCTYPE    CMDxpath();
  62. extern FUNCTYPE    CMD_unimp();
  63. #if    defined(DO_DO_XTHREAD)
  64. extern FUNCTYPE    CMDxthread();
  65. #endif    /* defined(DO_DO_XTHREAD) */
  66.  
  67. STATIC char    CMDfetchhelp[] = "[MessageID|Number]";
  68.  
  69. STATIC CMDENT    CMDtable[] = {
  70.     {    "authinfo",    CMDauthinfo,    FALSE,    3,    3,
  71.     "user Name|pass Password" },
  72.     {    "article",    CMDfetch,    FALSE,    1,    2,
  73.     CMDfetchhelp },
  74.     {    "body",        CMDfetch,    FALSE,    1,    2,
  75.     CMDfetchhelp },
  76.     {    "date",        CMDdate,    FALSE,    1,    1,
  77.     NULL },
  78.     {    "group",    CMDgroup,    FALSE,    2,    2,
  79.     "newsgroup" },
  80.     {    "head",        CMDfetch,    FALSE,    1,    2,
  81.     CMDfetchhelp },
  82.     {    "help",        CMDhelp,    FALSE,    1,    CMDany,
  83.     NULL },
  84.     {    "ihave",    CMD_unimp,    TRUE,    1,    2,
  85.     NULL },
  86.     {    "last",        CMDnextlast,    FALSE,    1,    1,
  87.     NULL },
  88.     {    "list",        CMDlist,    FALSE,    1,    2,
  89.     "[active|newsgroups|distributions|schema]" },
  90.     {    "listgroup",    CMDgroup,    FALSE,    1,    2,
  91.     "newsgroup" },
  92.     {    "mode",        CMDmode,    FALSE,    2,    2,
  93.     "reader" },
  94.     {    "newgroups",    CMDnewgroups,    FALSE,    3,    5,
  95.     "yymmdd hhmmss [\"GMT\"] [<distributions>]" },
  96.     {    "newnews",    CMDnewnews,    FALSE,    4,    6,
  97.     "newsgroups yymmdd hhmmss [\"GMT\"] [<distributions>]" },
  98.     {    "next",        CMDnextlast,    FALSE,    1,    1,
  99.     NULL },
  100.     {    "post",        CMDpost,    TRUE,    1,    1,
  101.     NULL },
  102.     {    "slave",    CMD_unimp,    FALSE,    1,    1,
  103.     NULL },
  104.     {    "stat",        CMDfetch,    FALSE,    1,    2,
  105.     CMDfetchhelp },
  106.     {    "xgtitle",    CMDxgtitle,    FALSE,    1,    2,
  107.     "[group_pattern]" },
  108.     {    "xhdr",        CMDxhdr,    FALSE,    2,    3,
  109.     "header [range|MessageID]" },
  110.     {    "xover",    CMDxover,    FALSE,    1,    2,
  111.     "[range]" },
  112.     {    "xpat",        CMDxpat,    FALSE,    4,    CMDany,
  113.     "header range|MessageID pat [morepat...]" },
  114.     {    "xpath",    CMDxpath,    FALSE,    2,    2,
  115.     "xpath MessageID" },
  116. #if    defined(DO_DO_XTHREAD)
  117.     {    "xthread",    CMDxthread,    FALSE,    1,    2,
  118.     "[dbinit|thread]" },
  119. #endif    /* defined(DO_DO_XTHREAD) */
  120.     {    NULL }
  121. };
  122.  
  123.  
  124. /*
  125. **  Log a summary status message and exit.
  126. */
  127. NORETURN
  128. ExitWithStats(x)
  129.     int            x;
  130. {
  131.     TIMEINFO        Now;
  132.     double        usertime;
  133.     double        systime;
  134.  
  135.     (void)fflush(stdout);
  136.     (void)GetTimeInfo(&Now);
  137.     STATfinish = TIMEINFOasDOUBLE(Now);
  138.     if (GetResourceUsage(&usertime, &systime) < 0) {
  139.     usertime = 0;
  140.     systime = 0;
  141.     }
  142.  
  143.     GRPreport();
  144.     syslog(L_NOTICE, "%s exit articles %ld groups %ld",
  145.        ClientHost, ARTcount, GRPcount);
  146.     if (POSTreceived ||  POSTrejected)
  147.     syslog(L_NOTICE, "%s posts received %ld rejected %ld",
  148.        ClientHost, POSTreceived, POSTrejected);
  149.     syslog(L_NOTICE, "%s times user %.3f system %.3f elapsed %.3f",
  150.     ClientHost, usertime, systime, STATfinish - STATstart);
  151.     exit(x);
  152. }
  153. /*
  154. **  The "help" command.
  155. */
  156. /* ARGSUSED0 */
  157. STATIC FUNCTYPE
  158. CMDhelp(ac, av)
  159.     int        ac;
  160.     char    *av[];
  161. {
  162.     CMDENT    *cp;
  163.  
  164.     Reply("%s\r\n", NNTP_HELP_FOLLOWS);
  165.     for (cp = CMDtable; cp->Name; cp++)
  166.     if (cp->Help == NULL)
  167.         Printf("  %s\r\n", cp->Name);
  168.     else
  169.         Printf("  %s %s\r\n", cp->Name, cp->Help);
  170.     Printf("Report problems to <%s@%s>\r\n",
  171.     NEWSMASTER, GetConfigValue(_CONF_FROMHOST));
  172.     Reply(".\r\n");
  173. }
  174.  
  175.  
  176. /*
  177. **  Unimplemented catch-all.
  178. */
  179. /* ARGSUSED0 */
  180. FUNCTYPE
  181. CMD_unimp(ac, av)
  182.     int        ac;
  183.     char    *av[];
  184. {
  185.     if (caseEQ(av[0], "ihave"))
  186.     Reply("%d Transfer permission denied\r\n", NNTP_AUTH_NEEDED_VAL);
  187.     else if (caseEQ(av[0], "slave"))
  188.     /* Somebody sends us this?  I don't believe it! */
  189.     Reply("%d Unsupported\r\n", NNTP_SLAVEOK_VAL);
  190.     else
  191.     Reply("%d %s not implemented; try help\r\n",
  192.         NNTP_BAD_COMMAND_VAL, av[0]);
  193. }
  194.  
  195.  
  196. /*
  197. **  Overwrite the original argv so that ps will show what's going on.
  198. */
  199. STATIC void
  200. TITLEset(what)
  201.     char        *what;
  202. {
  203. #if    !defined(HPUX)
  204.     register char    *p;
  205.     register int    i;
  206.     char        buff[BUFSIZ];
  207.  
  208.     /* Make ps think we're swapped out so we get "(nnrpd)" in the output. */
  209.     p = TITLEstart;
  210.     *p++ = '-';
  211.  
  212.     (void)sprintf(buff, "%s %s", ClientHost, what);
  213.     i = strlen(buff);
  214.     if (i > TITLEend - p - 2) {
  215.     i = TITLEend - p - 2;
  216.     buff[i] = '\0';
  217.     }
  218.     (void)strcpy(p, buff);
  219.     for (p += i; p < TITLEend; )
  220.     *p++ = ' ';
  221. #else
  222.     char        buff[BUFSIZ];
  223.  
  224.     (void)sprintf(buff, "(nnrpd) %s %s", ClientHost, what);
  225.     (void)pstat(PSTAT_SETCMD, buff, 0, 0, 0);
  226. #endif    /* defined(HPUX) */
  227. }
  228.  
  229.  
  230. #if    defined(DO_NNRP_GETHOSTBYADDR)
  231. /*
  232. **  Convert an IP address to a hostname.  Don't trust the reverse lookup,
  233. **  since anyone can fake .in-addr.arpa entries.
  234. */
  235. STATIC BOOL
  236. Address2Name(ap, hostname, i)
  237.     register INADDR        *ap;
  238.     register char        *hostname;
  239.     register int        i;
  240. {
  241.     register char        *p;
  242.     register struct hostent    *hp;
  243. #if    defined(h_addr)
  244.     register char        **pp;
  245. #endif
  246.  
  247.     /* Get the official hostname, store it away. */
  248.     if ((hp = gethostbyaddr((char *)ap, sizeof *ap, AF_INET)) == NULL)
  249.     return FALSE;
  250.     (void)strncpy(hostname, hp->h_name, i);
  251.     hostname[i - 1] = '\0';
  252.  
  253.     /* Get addresses for this host. */
  254.     if ((hp = gethostbyname(hostname)) == NULL)
  255.     return FALSE;
  256.  
  257.     /* Make sure one of those addresses is the address we got. */
  258. #if    defined(h_addr)
  259.     /* We have many addresses */
  260.     for (pp = hp->h_addr_list; *pp; pp++)
  261.     if (memcmp((POINTER)&ap->s_addr, (POINTER)*pp,
  262.         (SIZE_T)hp->h_length) == 0)
  263.         break;
  264.     if (*pp == NULL)
  265.     return FALSE;
  266. #else
  267.     /* We have one address. */
  268.     if (memcmp((POINTER)&ap->s_addr, (POINTER)hp->h_addr,
  269.         (SIZE_T)hp->h_length) != 0)
  270.     return FALSE;
  271. #endif
  272.  
  273.     /* Only needed for misconfigured YP/NIS systems. */
  274.     if (strchr(hostname, '.') == NULL
  275.      && (p = GetConfigValue(_CONF_DOMAIN)) != NULL) {
  276.     (void)strcat(hostname, ".");
  277.     (void)strcat(hostname, p);
  278.     }
  279.  
  280.     /* Make all lowercase, for wildmat. */
  281.     for (p = hostname; *p; p++)
  282.     if (CTYPE(isupper, *p))
  283.         *p = tolower(*p);
  284.     return TRUE;
  285. }
  286. #endif    /* defined(DO_NNRP_GETHOSTBYADDR) */
  287.  
  288.  
  289. BOOL
  290. PERMinfile(hp, ip, user, pass, accesslist)
  291.     char        *hp;
  292.     char        *ip;
  293.     char        *user;
  294.     char        *pass;
  295.     char        *accesslist;
  296. {
  297.     static char        ACCESS[] = _PATH_NNRPACCESS;
  298.     register FILE    *F;
  299.     register char    *p;
  300.     register BOOL    found;
  301.     register int    i;
  302.     char        buff[BIG_BUFFER];
  303.     char        *fields[5];
  304.  
  305.     if ((F = fopen(ACCESS, "r")) == NULL) {
  306.     syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACCESS);
  307.     return FALSE;
  308.     }
  309.  
  310.     PERMcanread = FALSE;
  311.     PERMcanpost = FALSE;
  312.     found = FALSE;
  313.     accesslist[0] = '\0';
  314.     while (fgets(buff, sizeof buff, F) != NULL) {
  315.     if ((p = strchr(buff, '\n')) != NULL)
  316.         *p = '\0';
  317.     if ((p = strchr(buff, COMMENT_CHAR)) != NULL)
  318.         *p = '\0';
  319.     if (buff[0] == '\0')
  320.         continue;
  321.  
  322.     /* Split "host:permissions:user:pass:groups" into fields. */
  323.     for (fields[0] = buff, i = 0, p = buff; *p; p++)
  324.         if (*p == ':') {
  325.         *p = '\0';
  326.         fields[++i] = p + 1;
  327.         }
  328.     if (i != 4)
  329.         /* Malformed line. */
  330.         continue;
  331.  
  332.     if (hp)
  333.         /* Got an address; try to match either the IP address or as
  334.          * a text hostname. */
  335.         if (!(ip && wildmat(ip, fields[0])) && !wildmat(hp, fields[0]))
  336.         continue;
  337.     /* Matching for a specific user or just the host? */
  338.     if (user && (!EQ(user, fields[2]) || !EQ(pass, fields[3])))
  339.         continue;
  340.  
  341.     PERMcanread = strchr(fields[1], 'R') != NULL;
  342.     PERMcanpost = strchr(fields[1], 'P') != NULL;
  343.     (void)strcpy(PERMuser, fields[2]);
  344.     (void)strcpy(PERMpass, fields[3]);
  345.     (void)strcpy(accesslist, fields[4]);
  346.     found = TRUE;
  347.     }
  348.     (void)fclose(F);
  349.     return found;
  350. }
  351.  
  352.  
  353. /*
  354. **  Determine access rights of the client.
  355. */
  356. STATIC void
  357. StartConnection(accesslist)
  358.     char        *accesslist;
  359. {
  360.     struct sockaddr_in    sin;
  361.     int            length;
  362.     char        buff[SMBUF];
  363.     char        *ClientAddr;
  364.  
  365.     /* Get the peer's name. */
  366.     length = sizeof sin;
  367.     ClientAddr = NULL;
  368.     if (getpeername(STDIN, (struct sockaddr *)&sin, &length) < 0) {
  369.     if (!isatty(STDIN)) {
  370.         syslog(L_ERROR, "%s cant getpeername %m", "?");
  371.         Printf("%d I can't get your name.  Goodbye.\r\n", NNTP_ACCESS_VAL);
  372.         ExitWithStats(1);
  373.     }
  374.     (void)strcpy(ClientHost, "stdin");
  375.     }
  376.     else {
  377.     if (sin.sin_family != AF_INET) {
  378.         syslog(L_ERROR, "%s bad_address_family %ld",
  379.         "?", (long)sin.sin_family);
  380.         Printf("%d Bad address family.  Goodbye.\r\n", NNTP_ACCESS_VAL);
  381.         ExitWithStats(1);
  382.     }
  383.  
  384.     /* Get client's name. */
  385. #if    defined(DO_NNRP_GETHOSTBYADDR)
  386.     if (!Address2Name(&sin.sin_addr, ClientHost, sizeof ClientHost)) {
  387.         (void)strcpy(ClientHost, inet_ntoa(sin.sin_addr));
  388.         syslog(L_ERROR, "? cant gethostbyaddr %s %m", ClientHost);
  389.     }
  390.     else {
  391.         ClientAddr = buff;
  392.         (void)strcpy(buff, inet_ntoa(sin.sin_addr));
  393.     }
  394. #else
  395.     (void)strcpy(ClientHost, inet_ntoa(sin.sin_addr));
  396. #endif /* defined(DO_NNRP_GETHOSTBYADDR) */
  397.     }
  398.  
  399.     syslog(L_NOTICE, "%s connect", ClientHost);
  400.     if (!PERMinfile(ClientHost, ClientAddr, (char *)NULL, (char *)NULL,
  401.         accesslist)) {
  402.     syslog(L_NOTICE, "%s no_access", ClientHost);
  403.     Printf("%d You are not in my access file.  Goodbye.\r\n",
  404.         NNTP_ACCESS_VAL);
  405.     ExitWithStats(1);
  406.     }
  407. }
  408.  
  409.  
  410. #if    !defined(VAR_NONE)
  411.  
  412. #if    defined(VAR_VARARGS)
  413. #if    defined(lint)
  414. #define START_VARARG(fmt, vp, type)    va_start(vp); fmt = NULL
  415. #else
  416. #define START_VARARG(fmt, vp, type)    va_start(vp); fmt = va_arg(vp, type)
  417. #endif    /* defined(lint) */
  418. #endif    /* defined(VAR_VARARGS) */
  419. #if    defined(VAR_STDARGS)
  420. #define START_VARARG(fmt, vp, type)    va_start(vp, fmt)
  421. #endif    /* defined(VAR_STDARGS) */
  422.  
  423. /*
  424. **  Send a reply, possibly with debugging output.
  425. */
  426. /*VARARGS*/
  427. void
  428. #if    defined(VAR_VARARGS)
  429. Reply(va_alist)
  430.     va_dcl
  431. #endif    /* defined(VAR_VARARGS) */
  432. #if    defined(VAR_STDARGS)
  433. Reply(char *fmt, ...)
  434. #endif    /* defined(VAR_STDARGS) */
  435. {
  436.     register int    oerrno;
  437.     register char    *p;
  438.     va_list        vp;
  439.     char        buff[2048];
  440. #if    defined(VAR_VARARGS)
  441.     register char    *fmt;
  442. #endif    /* defined(VAR_VARARGS) */
  443.  
  444.     START_VARARG(fmt, vp, char*);
  445.     (void)vprintf(fmt, vp);
  446.     va_end(vp);
  447.  
  448.     if (Tracing) {
  449.     oerrno = errno;
  450.     START_VARARG(fmt, vp, char*);
  451.  
  452.     /* Copy output, but strip trailing CR-LF. */
  453.     (void)vsprintf(buff, fmt, vp);
  454.     p = buff + strlen(buff) - 1;
  455.     while (p >= buff && (*p == '\n' || *p == '\r'))
  456.         *p-- = '\0';
  457.     syslog(LOG_DEBUG, "%s > %s", ClientHost, buff);
  458.  
  459.     va_end(vp);
  460.     errno = oerrno;
  461.     }
  462. }
  463. #endif    /* !defined(VAR_NONE) */
  464.  
  465.  
  466. /*
  467. **  Got a signal; toggle tracing.
  468. */
  469. STATIC SIGHANDLER
  470. ToggleTrace(s)
  471.     int        s;
  472. {
  473.     ChangeTrace = TRUE;
  474.     (void)signal(s, ToggleTrace);
  475. }
  476.  
  477.  
  478. /*
  479. **  Print a usage message and exit.
  480. */
  481. STATIC void
  482. Usage()
  483. {
  484.     (void)fprintf(stderr, "Usage error.\n");
  485.     exit(1);
  486. }
  487.  
  488.  
  489. /* ARGSUSED0 */
  490. int
  491. main(argc, argv)
  492.     int            argc;
  493.     char        *argv[];
  494. {
  495. #if    NNRP_LOADLIMIT > 0
  496.     int            load;
  497. #endif    /* NNRP_LOADLIMIT > 0 */
  498.     CMDENT        *cp;
  499.     char        buff[NNTP_STRLEN];
  500.     char        **av;
  501.     int            ac;
  502.     READTYPE        r;
  503.     TIMEINFO        Now;
  504.     register int    i;
  505.     char        *Reject;
  506.     char        accesslist[BIG_BUFFER];
  507.  
  508. #if    !defined(HPUX)
  509.     /* Save start and extent of argv for TITLEset. */
  510.     TITLEstart = argv[0];
  511.     TITLEend = argv[argc - 1] + strlen(argv[argc - 1]) - 1;
  512. #endif    /* !defined(HPUX) */
  513.  
  514.     /* Parse arguments.   Must COPY() optarg if used because the
  515.      * TITLEset() routine would clobber it! */
  516.     Reject = NULL;
  517.     while ((i = getopt(argc, argv, "S:r:s:t")) != EOF)
  518.     switch (i) {
  519.     default:
  520.         Usage();
  521.         /* NOTREACHED */
  522.     case 'S':            /* We're a slave to NNTP master */
  523.         RemoteMaster = COPY(optarg);
  524.         break;
  525.     case 's':            /* Unused title string */
  526.         break;
  527.     case 't':            /* Tracing */
  528.         Tracing = TRUE;
  529.         break;
  530.     case 'r':            /* Reject connection message */
  531.         Reject = COPY(optarg);
  532.         break;
  533.     }
  534.     argc -= optind;
  535.     if (argc)
  536.     Usage();
  537.  
  538.     /* Setup. */
  539.     openlog("nnrpd", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
  540.     if (GetTimeInfo(&Now) < 0) {
  541.     syslog(L_FATAL, "cant gettimeinfo %m");
  542.     exit(1);
  543.     }
  544.     STATstart = TIMEINFOasDOUBLE(Now);
  545.  
  546.     if ((MyHostName = GetConfigValue(_CONF_PATHHOST)) == NULL) {
  547.     syslog(L_FATAL, "cant getconfigvalue %m");
  548.     ExitWithStats(1);
  549.     }
  550.     MyHostName = COPY(MyHostName);
  551.  
  552. #if    NNRP_LOADLIMIT > 0
  553.     if ((load = GetLoadAverage()) > NNRP_LOADLIMIT) {
  554.     syslog(L_NOTICE, "load %d > %d", load, NNRP_LOADLIMIT);
  555.     Reply("%d load at %d, try later\r\n", NNTP_GOODBYE_VAL, load);
  556.     ExitWithStats(1);
  557.     }
  558. #endif    /* NNRP_LOADLIMIT > 0 */
  559.  
  560.     /* Ignore SIGPIPE, since we'll see closed connections with read. */
  561.     (void)signal(SIGPIPE, SIG_IGN);
  562.  
  563.     /* Arrange to toggle tracing. */
  564.     (void)signal(SIGHUP, ToggleTrace);
  565.  
  566.     /* Get permissions and see if we can talk to this client */
  567.     StartConnection(accesslist);
  568.     if (!PERMcanread && !PERMcanpost) {
  569.     syslog(L_NOTICE, "%s no_permission", ClientHost);
  570.     Printf("%d You have no permission to talk.  Goodbye.\r\n",
  571.            NNTP_ACCESS_VAL);
  572.     ExitWithStats(1);
  573.     }
  574.  
  575.     /* Proceed with initialization. */
  576.     PERMneedauth = PERMuser[0] != '\0' && PERMpass != '\0';
  577.     PERMspecified = NGgetlist(&PERMlist, accesslist);
  578.     TITLEset("connect");
  579.  
  580.     /* Were we told to reject connections? */
  581.     if (Reject) {
  582.     syslog(L_NOTICE, "%s rejected %s", ClientHost, Reject);
  583.     Reply("%s %s\r\n", NNTP_GOODBYE, Reject);
  584.     ExitWithStats(0);
  585.     }
  586.  
  587.     ARTreadschema();
  588.     if (!GetGroupList()) {
  589.     /* This shouldn't really happen. */
  590.     syslog(L_NOTICE, "%s cant getgrouplist %m", ClientHost);
  591.     Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
  592.     ExitWithStats(1);
  593.     }
  594.  
  595.     Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
  596.        PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
  597.        MyHostName, INNVersion(),
  598.        PERMcanpost ? "posting ok" : "no posting");
  599.  
  600.     /* Main dispatch loop. */
  601.     for (av = NULL; ; ) {
  602.     (void)fflush(stdout);
  603.     if (ChangeTrace) {
  604.         Tracing = Tracing ? FALSE : TRUE;
  605.         syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
  606.         ChangeTrace = FALSE;
  607.     }
  608.     switch (r = READline(buff, (int)sizeof buff, CLIENT_TIMEOUT)) {
  609.     default:
  610.         syslog(L_ERROR, "%s internal %d in main", ClientHost, r);
  611.         /* FALLTHROUGH */
  612.     case RTtimeout:
  613.         syslog(L_NOTICE, "%s timeout", ClientHost);
  614.         Printf("%d Timeout after %d seconds, closing connection.\r\n",
  615.            NNTP_TEMPERR_VAL, CLIENT_TIMEOUT);
  616.         ExitWithStats(1);
  617.         break;
  618.     case RTlong:
  619.         Reply("%d Line too long\r\n", NNTP_BAD_COMMAND_VAL);
  620.         continue;
  621.     case RTok:
  622.         /* Do some input processing, check for blank line. */
  623.         if (buff[0] == '\0')
  624.         continue;
  625.         if (Tracing)
  626.         syslog(L_TRACE, "%s < %s", ClientHost, buff);
  627.         ac = Argify(buff, &av);
  628.         if (ac == 0)
  629.         continue;
  630.         break;
  631.     case RTeof:
  632.         /* Handled below. */
  633.         break;
  634.     }
  635.  
  636.     /* Client gone? */
  637.     if (r == RTeof || caseEQ(av[0], "quit"))
  638.         break;
  639.  
  640.     /* Find command. */
  641.     for (cp = CMDtable; cp->Name; cp++)
  642.         if (caseEQ(cp->Name, av[0]))
  643.         break;
  644.     if (cp->Name == NULL) {
  645.         if ((int)strlen(buff) > 40)
  646.         syslog(L_NOTICE, "%s unrecognized %.40s...", ClientHost, buff);
  647.         else
  648.         syslog(L_NOTICE, "%s unrecognized %s", ClientHost, buff);
  649.         Reply("%d What?\r\n", NNTP_BAD_COMMAND_VAL);
  650.         continue;
  651.     }
  652.  
  653.     /* Check usage. */
  654.     if ((cp->Minac != CMDany && ac < cp->Minac)
  655.      || (cp->Maxac != CMDany && ac > cp->Maxac)) {
  656.         Reply("%d %s\r\n",
  657.         NNTP_SYNTAX_VAL,  cp->Help ? cp->Help : "Usage error");
  658.         continue;
  659.     }
  660.  
  661.     /* Check permissions and dispatch. */
  662.     if (cp->Needauth && PERMneedauth) {
  663.         Reply("%d Authentication required for command\r\n",
  664.         NNTP_AUTH_NEEDED_VAL);
  665.         continue;
  666.     }
  667.     TITLEset(av[0]);
  668.     (*cp->Function)(ac, av);
  669.     }
  670.  
  671.     Reply("%s\r\n", NNTP_GOODBYE_ACK);
  672.  
  673.     ExitWithStats(0);
  674.     /* NOTREACHED */
  675. }
  676.